#!/usr/bin/bash
#
# This file and its contents are supplied under the terms of the
# Common Development and Distribution License ("CDDL"), version 1.0.
# You may only use this file in accordance with the terms of version
# 1.0 of the CDDL.
#
# A full copy of the text of the CDDL should have accompanied this
# source.  A copy of the CDDL is also available via the Internet at
# http://www.illumos.org/license/CDDL.
#
# Copyright 2020 Joyent, Inc.
#

#
# build_strap - populate proto.strap
#
# This script attempts to download a pre-built proto.strap, depending on the
# pkgsrc branch and projects/illumos-extra git HEAD to determine the right
# tarball.  This avoids a considerable amount of time building.
#
# Cached bootstrap directories are kept at /opt/SmartOS/build-cache, and
# symlinked into the build area; this allows multiple different builds to share
# a cached strap dir.
#
# If we can't find a suitable tarball, or we find a non-symlinked proto.strap,
# we'll fall back to doing a full strap build of illumos-extra.  This is also
# true if the user specified non-default compilers.
#
# In "upload" mode, we build a strap, then upload it to Manta for use by other
# builds.
#

usage="$0 make -a adjunct.tar -j maxjobs -d protodir [-f] [-o tarball]"
usage="$usage | upload -D bits -d path -t timestamp"
wsroot=$(cd $(dirname $0)/../; pwd)
pkgin_file="/opt/local/etc/pkgin/repositories.conf"
manta_host="https://us-east.manta.joyent.com"
manta_dir="/Joyent_Dev/public/builds/SmartOS/strap-cache/master"
cache_base="/opt/SmartOS/build-cache/"
srcdir="$wsroot/projects/illumos-extra"
adjunct=""
protodir=""
max_jobs="128"
force_build="no"
tarfile=""

function fatal
{
        local msg="$*"
        [[ -z "$msg" ]] && msg="failed"
        echo "$msg" >&2
        exit 1
}

function verbose
{
	echo "$@"
	"$@"
}

function identify_pkgsrc_branch
{
	local branch=$($wsroot/tools/pkgsrc_branch.nawk "$pkgin_file")
	[[ $? -ne 0 ]] && fatal "failed to extract pkgsrc branch"
	[[ -z "$branch" ]] && fatal "pkgsrc branch is empty"
	echo $branch
}

function identify_srcsha
{
	local sha=$(cd $srcdir && git rev-list -1 HEAD)
	[[ $? -ne 0 ]] && fatal "failed to extract illumos-extra SHA"
	[[ -z "$sha" ]] && fatal "illumos-extra SHA is empty"
	echo $sha
}

function build_strap
{
	echo "Building illumos-extra bootstrap at $protodir"

	#
	# Strip any double slashes. Certain pieces of software simply hate you -
	# yes, YOU - and get very confused given double slashes. Namely
	# projects/illumos-extra/make
	#
	DESTDIR=$(echo $protodir | sed 's+//*+/+g')

	verbose gmake STRAP=strap MAX_JOBS=$max_jobs DESTDIR=$DESTDIR \
	    -C $wsroot/projects/illumos-extra install_strap ||
	    fatal "failed to build install_strap"

	verbose gtar xzf $adjunct -C "$protodir" ||
	    fatal "failed to extract adjunct"
}

#
# Download a copy of the proto.strap we need, if there is one. If curl fails,
# we'll presume it's because there isn't a matching tarball available, and fall
# back to building locally.
#
function download
{
	local pkgsrc_branch=$1
	local srcdirsha=$2
	local cache_dir="$cache_base/$pkgsrc_branch/$srcdirsha"
	local outfile="/var/tmp/$srcdirsha.proto.strap.tar.gz.$$"
	local tmpdir="$cache_dir.$$"
	local latest="$manta_dir/$pkgsrc_branch/$srcdirsha/latest"
	local url="$(/usr/bin/curl -sS $manta_host/$latest 2>/dev/null)"

	if [[ -z "$url" ]]; then
		echo "Failed to find $latest: building locally"
		build_strap
		return 0
	fi

	url="$manta_host/$url/proto.strap.tar.gz"
	echo "Downloading proto.strap contents from $url ..."

	if ! /usr/bin/curl -f -o "$outfile" "$url"; then
		rm -f "$outfile"
		echo "Failed to download $url: building locally"
		build_strap
		return 0
	fi

	pfexec mkdir -p "$tmpdir" || fatal "failed to make $tmpdir"

	if ! pfexec tar xzf "$outfile" -C "$tmpdir"; then
		rm -f "$outfile"
		pfexec rm -rf "$tmpdir"
		fatal "failed to extract tar file"
	fi

	rm -f "$outfile"

	if ! pfexec mv "$tmpdir" "$cache_dir"; then
		#
		# It's possible we lost a rename race. If we did, then let's not
		# worry about that and roll with it and just remove our download
		# and copy. However, we need to remove our local copy anyway.
		#
		pfexec rm -rf "$tmpdir"
		if [[ ! -d "$cache_dir" ]]; then
			fatal "failed to rename our temporary directory to " \
			    "$cache_dir"
		fi
	fi
}

#
# Do a strap build into the cache directory, then create the tarball for
# uploading.
#
function create_cache
{
	local cache_dir="$1"
	local tarfile="$2"

	if [[ "$force_build" = "yes" || ! -d "$cache_dir" ]]; then
		pfexec mkdir -p "$cache_dir" || \
		    fatal "couldn't mkdir $cache_dir"
		pfexec chown $(id -u) "$cache_dir" || \
		    fatal "couldn't chown $cache_dir"

		protodir="$cache_dir"

		build_strap
	fi

	mkdir -p $(dirname $tarfile) ||
	    fatal "failed to mkdir for $tarfile"

	pfexec gtar -C "$cache_dir" -czf "$tarfile" . ||
	    fatal "failed to create $tarfile"

	pfexec chown $(id -u) "$tarfile" || \
	    fatal "couldn't chown $tarfile"

	echo "Created $tarfile."
}

#
# Populate proto.strap: either we do a local build, or we download and use a
# cached copy, sym-linking it in.
#
function populate_strap
{
	local pkgsrc_branch=$(identify_pkgsrc_branch)
	local srcdirsha=$(identify_srcsha)
	local cache_dir="$cache_base/$pkgsrc_branch/$srcdirsha"

	while getopts "a:d:fhj:o:" arg; do
		case $arg in
		a)
			adjunct=$OPTARG ;;
		d)
			protodir=$OPTARG ;;
		f)
			force_build=yes ;;
		h)
			echo "$usage"
			exit 0 ;;
		j)
			max_jobs=$OPTARG ;;
		o)
			tarfile=$OPTARG ;;
		?)
			echo "$usage" >&2
			exit 1 ;;
		esac
	done

	[[ -n "$adjunct" ]] || fatal "missing -a argument"
	[[ -n "$protodir" ]] || fatal "missing -d argument"

	if [[ -e "$cache_dir" && ! -d "$cache_dir" ]]; then
		fatal "found weird non-directory $cache_dir"
	fi

	if [[ -n "$tarfile" ]]; then
		create_cache $cache_dir $tarfile
		return
	fi

	#
	# First we should clean up: if not a symlink or empty dir, it must be a
	# local strap build we need.
	#
	if [[ -d "$protodir" ]];then
		rm -f "$protodir" 2>/dev/null || true
		rmdir "$protodir" 2>/dev/null || true
		if [[ -d "$protodir" ]]; then
			build_strap
			return 0
		fi
	fi

	if [[ "$force_build" = "yes" ]]; then
		build_strap
		return 0
	fi

	if [[ ! -d "$cache_dir" ]]; then
		download $pkgsrc_branch $srcdirsha
	fi

	verbose ln -s "$cache_dir" "$protodir" ||
	    fatal "failed to symlink $protodir"

}

#
# Upload a proto.strap tarball.
#
function upload_strap
{
	local pkgsrc_branch=$(identify_pkgsrc_branch)
	local srcdirsha=$(identify_srcsha)
	local bits_dir=""
	local upload_dir=""
	local timestamp=""

	while getopts "D:d:ht:" arg; do
		case $arg in
		D)
			bits_dir=$OPTARG ;;
		d)
			upload_dir=$OPTARG ;;
		h)
			echo "$usage"
			exit 0 ;;
		t)
			timestamp=$OPTARG ;;
		?)
			echo "$usage" >&2
			exit 1 ;;
		esac
	done

	[[ -n "$bits_dir" ]] || fatal "missing -D argument"
	[[ -n "$upload_dir" ]] || fatal "missing -d argument"
	[[ -n "$timestamp" ]] || fatal "missing -t argument"

	mdir="/$MANTA_USER/$upload_dir/$pkgsrc_branch/$srcdirsha/"
	mbdir="$mdir/$timestamp"

	verbose mmkdir -p "$mbdir"

	for file in $bits_dir/*; do
		verbose mput -f $file $mbdir/$(basename $file)
	done

	echo $mbdir | mput -H "content-type: text/plain" "$mdir/latest"
}

export PATH=/usr/bin:/usr/sbin:/sbin:/opt/local/bin:/opt/local/sbin:$PATH
set -o pipefail
set -e

cmd=$1
shift

case $cmd in
	make) populate_strap $* ;;
	upload) upload_strap $* ;;
	*)
		echo "$usage" >&2
		exit 1 ;;
esac
